using IQIGame.Onigao.Framework.Serialization;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using UnityEditor;
using UnityEngine;

namespace IQIGame.Onigao.Game.Editors
{
    public class CfgFSMExporter
    {
        private Dictionary<string, CfgFSMStateNode> _stateMap = new Dictionary<string, CfgFSMStateNode>();
        private List<CfgFSMConditionNode> _conditionList = new List<CfgFSMConditionNode>();
        private string _rootPath;
        private string _filePath;

        public CfgFSMExporter(string rootPath, string file)
        {
            this._rootPath = rootPath;
            this._filePath = file;
        }

        public void AddStateCfg(XmlElement element)
        {
            CfgFSMStateNode state = new CfgFSMStateNode();
            state.Init(element, GetModulePath());
            _stateMap.TryAdd(state.id, state);
        }

        public void AddConditionCfg(XmlElement element)
        {
            CfgFSMConditionNode condition = new CfgFSMConditionNode();
            condition.Init(element, GetModulePath());
            _conditionList.Add(condition);
        }

        public bool InitAfterNodeParsed()
        {
            Dictionary<string, List<CfgFSMConditionNode>> sourceMap = new Dictionary<string, List<CfgFSMConditionNode>>();
            HashSet<string> targetMap = new HashSet<string>();
            CfgFSMStateNode entry = null;
            foreach (var cond in _conditionList)
            {
                if (!sourceMap.TryGetValue(cond.sourceId, out var conds))
                {
                    conds = new List<CfgFSMConditionNode>();
                    sourceMap.Add(cond.sourceId, conds);
                }
                conds.Add(cond);
                targetMap.Add(cond.targetId);
            }
            foreach (var kv in _stateMap)
            {
                var stateNode = kv.Value;
                bool isSource = sourceMap.TryGetValue(stateNode.id, out var condNodes);
                bool isTarget = targetMap.Contains(stateNode.id);
                if (isSource && !isTarget)
                {
                    if (entry != null)
                    {
                        Debug.LogError($"导出状态机错误：包含了多个entry：{stateNode.name}, {entry.name}。状态机配置：{_filePath}");
                        return false;
                    }
                    entry = kv.Value;
                }

                if (isSource)
                {
                    stateNode.ConditionNodes = condNodes.Clone();
                }
            }
            if (entry == null)
            {
                Debug.LogError("导出状态机错误：找不到状态机的入口Action，请确认是否状态机没有Action或只有唯一的一个Action。状态机配置：" + _filePath);
                return false;
            }
            entry.isEntry = true;
            return true;
        }

        /// <summary>
        /// 导出配置数据
        /// </summary>
        public void ExportData()
        {
            string fileName = GetModulePath();
            string dirPath = $"{Application.dataPath}/Res/25_ConfigurableFSM/{fileName.Substring(0, fileName.LastIndexOf('/'))}";
            string expPath = $"{Application.dataPath}/Res/25_ConfigurableFSM/{fileName}.bytes";

            CfgFSMStateNode entry = null;
            int intId = 1;
            Dictionary<string, int> strIdToIntIdMap = new Dictionary<string, int>();
            foreach (var kv in _stateMap)
            {
                var stateNode = kv.Value;
                if (stateNode.isEntry)
                {
                    entry = stateNode;
                }
                strIdToIntIdMap.Add(stateNode.id, intId);
                intId++;
            }
            foreach (var cond in _conditionList)
            {
                strIdToIntIdMap.Add(cond.id, intId);
                intId++;
            }

            CFSMConfig fSMConfig = new CFSMConfig();
            fSMConfig.name = fileName;
            fSMConfig.conditions = new List<CFSMConditionCfg>();
            foreach (var cond in _conditionList)
            {
                fSMConfig.conditions.Add(cond.ToConfig(strIdToIntIdMap));
            }
            fSMConfig.states = new List<CFSMStateCfg>
            {
                entry.ToConfig(strIdToIntIdMap)
            };
            foreach (var kv in _stateMap)
            {
                var state = kv.Value;
                if (state != entry)
                {
                    fSMConfig.states.Add(state.ToConfig(strIdToIntIdMap));
                }
            }

            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
            }

            ByteBuf byteBuf = new ByteBuf();
            fSMConfig.Serialize(byteBuf);
            System.IO.File.WriteAllBytes(expPath, byteBuf.Bytes);
        }

        public static CfgFSMExporter Create(string fsmConfigDir, string file)
        {
            CfgFSMExporter exporter = new CfgFSMExporter(fsmConfigDir, file);
            XmlDocument doc = new XmlDocument();
            doc.Load(file);

            var rootNodes = doc.GetElementsByTagName("graphml");
            var rootNodes2 = rootNodes.Item(0).ChildNodes;
            XmlElement graphRoot = null;
            foreach (var node in rootNodes2)
            {
                if (node is XmlElement element && element.Name == "graph")
                {
                    graphRoot = element;
                    break;
                }
            }
            var nodes = graphRoot.GetElementsByTagName("node");
            foreach (object node in nodes)
            {
                if (node is XmlElement element)
                {
                    exporter.AddStateCfg(element);
                }
            }
            var edges = graphRoot.GetElementsByTagName("edge");
            foreach (object node in edges)
            {
                if (node is XmlElement element)
                {
                    exporter.AddConditionCfg(element);
                }
            }
            bool isInitSuccess = exporter.InitAfterNodeParsed();
            if (!isInitSuccess)
            {
                return null;
            }
            return exporter;
        }

        public bool CheckExpiredCode(List<string> expiredCodePaths)
        {
            string codeDirPath = GetCodeDirPath();
            if (!Directory.Exists(codeDirPath))
            {
                return false;
            }
            string[] allCodeFiles = Directory.GetFiles(codeDirPath, "*.cs", SearchOption.AllDirectories);
            List<string> allCodeNames = new List<string>();
            foreach (string codeName in allCodeFiles)
            {
                allCodeNames.Add(Path.GetFileNameWithoutExtension(codeName));
            }
            HashSet<string> curFileNames = new HashSet<string>();
            foreach (var kv in _stateMap)
            {
                CfgFSMStateNode state = kv.Value;
                curFileNames.Add(state.GetCodeFileName(out _));
            }
            foreach (var cond in _conditionList)
            {
                foreach (var part in cond.conditionParts)
                {
                    if (!part.isPublic)
                    {
                        curFileNames.Add(part.GetCodeFileName(cond.fsmShortPath, out _));
                    }
                }
            }
            //生成Data class
            curFileNames.Add(GetDataCodeFileName());
            foreach (var fileName in curFileNames)
            {
                allCodeNames.Remove(fileName);
            }
            foreach (var fileName in allCodeNames)
            {
                expiredCodePaths.Add($"{codeDirPath}/{fileName}.cs");
            }
            return expiredCodePaths.Count > 0;
        }

        private string GetCodeDirPath()
        {
            string fileShortPath = GetModulePath();
            return $"{GetCodeRoorPath()}/{fileShortPath}";
        }

        private string GetModulePath()
        {
            return EdtCfgFSMUtil.GetCfgFileName(_rootPath, _filePath);
        }

        private string GetCodeRoorPath()
        {
            return $"{Application.dataPath}/Scripts/GamePlay/Runtime/Module/Share/CFSM";
        }

        /// <summary>
        /// 生成代码
        /// </summary>
        /// <param name="entityClassName"></param>
        public void ExportCode(string entityClassName)
        {
            //生成condition func和state的代码
            string codeRootpath = GetCodeRoorPath();
            string codeDirPath = GetCodeDirPath();
            foreach (var cond in _conditionList)
            {
                cond.GenCode(codeRootpath, codeDirPath, entityClassName);
            }
            foreach (var kv in _stateMap)
            {
                var state = kv.Value;
                state.GenCode(codeRootpath, codeDirPath, entityClassName);
            }

            //生成黑板代码(用于在runtime里根据名称创建状态机condition func和state)
            //扫描所有现有的ConditionFunc和State的代码文件，缓存起来，用于生成代码
            string[] filePaths = Directory.GetFiles(codeRootpath, "*.cs", SearchOption.AllDirectories);
            //公共条件
            //List<string> commonCondFuncs = new List<string>();
            //私有条件
            Dictionary<string, List<string>> privateCondFuncDict = new Dictionary<string, List<string>>();
            //状态
            Dictionary<string, List<string>> stateDict = new Dictionary<string, List<string>>();
            foreach (string path in filePaths)
            {
                string fileNameWithShortDir = EdtCfgFSMUtil.GetCfgFileName(codeRootpath, path);
                string classFileName = Path.GetFileNameWithoutExtension(path);
                //排除根目录下的代码
                if (classFileName == fileNameWithShortDir)
                {
                    continue;
                }
                //获取代码文件子目录（除去代码根目录以外的短路径）
                //+1是为了去除文件名前面的/
                string moduleName = fileNameWithShortDir.Substring(0, fileNameWithShortDir.Length - (classFileName.Length + 1));
                //condition func
                if (classFileName.StartsWith(EdtCFSMConst.CondFuncCSFilePrefix))
                {
                    if (moduleName == EdtCFSMConst.CommonCondFuncRoot) //公共condition func
                    {
                        //commonCondFuncs.Add(classFileName);
                    }
                    else //私有condition func
                    {
                        if (!privateCondFuncDict.TryGetValue(moduleName, out var list))
                        {
                            list = new List<string>();
                            privateCondFuncDict.Add(moduleName, list);
                        }
                        list.Add(classFileName);
                    }
                }
                //state
                else if (classFileName.StartsWith(EdtCFSMConst.StateCSFilePrefix))
                {
                    if (!stateDict.TryGetValue(moduleName, out var list))
                    {
                        list = new List<string>();
                        stateDict.Add(moduleName, list);
                    }
                    list.Add(classFileName);
                }
            }
            //生成ConditionFunc代码片段
            //            //公有条件，后续拼装到if代码块里，见state拼装之后的代码
            //            string condFuncCodeFragment1 = string.Empty;
            //            foreach (var classFileName in commonCondFuncs)
            //            {
            //                //拼装所有case classfile name...
            //                string name = GetCfgNameByFileName(classFileName);
            //                condFuncCodeFragment1 += @$"                        case ""{name}"":
            //                            obj = ClassPool.Get<{classFileName}>();
            //                            break;
            //";
            //            }
            //私有条件，后续拼装到最外层的switch里面（见【注释1】的代码）
            string privateCondFuncCodeFrag = string.Empty;
            foreach (var kv in privateCondFuncDict)
            {
                string innerFragment = string.Empty;
                //先拼最里面的case，该case代表的是condition func名字和类的对应关系
                foreach (var classFileName in kv.Value)
                {
                    string name = GetCfgNameByFileName(classFileName);
                    innerFragment += @$"                        case ""{name}"":
                            obj = ClassPool.Get<{classFileName}>();
                            break;
";
                }
                //上面所有属于该状态机（kv.Key）配置的condition func的switch case拼装完以后
                //放到switch里面，作为该状态机（kv.Key）的全部switch case
                innerFragment = $@"                case ""{kv.Key}"":
                    switch (name)
                    {{
{innerFragment}
                        default:
                            break;
                    }}
                    break;
";
                //所有每一个状态机的switch case代码，后续拼装到condition func创建方法的最外层switch里面
                privateCondFuncCodeFrag += innerFragment;
            }

            //生成State代码片段
            string stateCodeFragment = string.Empty;
            foreach (var kv in stateDict)
            {
                string innerFragment = string.Empty;
                //先拼最里面的case，该case代表的是state名字和类的对应关系
                foreach (var classFileName in kv.Value)
                {
                    string name = GetCfgNameByFileName(classFileName);
                    innerFragment += @$"                        case ""{name}"":
                            obj = ClassPool.Get<{classFileName}>();
                            break;
";
                }
                //上面所有属于该状态机（kv.Key）配置的state的switch case拼装完以后
                //放到switch里面，作为该状态机（kv.Key）的全部switch case
                innerFragment = $@"                case ""{kv.Key}"":
                    switch (name)
                    {{
{innerFragment}
                        default:
                            break;
                    }}
                    break;
";
                //所有每一个状态机的switch case代码，后续拼装到state创建方法的最外层switch里面
                stateCodeFragment += innerFragment;
            }

            //注释1：拼装condFunc和state的最终全部代码
            string allCondFragment = @$"            switch (fsmName)
            {{
{privateCondFuncCodeFrag}
                default:
                    break;
            }}";
            string allStateFragment = @$"            switch (fsmName)
            {{
{stateCodeFragment}
                default:
                    break;
            }}";
            //加载模板（Blackboard.template），替换两个方法Region块里的内容
            string templatePath = Application.dataPath + "/Scripts/FrameworkExt/Editor/ConfigurableFSM/Blackboard.template";
            string templateContent = File.ReadAllText(templatePath);
            string oldCondRegion = string.Format(EdtCFSMConst.BlackboardConditionRegion, string.Empty);
            string newCondRegion = string.Format(EdtCFSMConst.BlackboardConditionRegion, allCondFragment);
            string code = templateContent.Replace(oldCondRegion, newCondRegion);
            string oldStateRegion = string.Format(EdtCFSMConst.BlackboardStateRegion, string.Empty);
            string newStateRegion = string.Format(EdtCFSMConst.BlackboardStateRegion, allStateFragment);
            code = code.Replace(oldStateRegion, newStateRegion);
            //生成到文件
            string blackboardCodePath = Application.dataPath + "/Scripts/GamePlay/Runtime/Module/Share/CFSM/CFSMBlackBoard.cs";
            File.WriteAllText(blackboardCodePath, code);
            //生成Data class
            string dataCodeFileName = GetDataCodeFileName();
            string dataContent = $@"using IQIGame.Onigao.Game;

namespace IQIGame.Onigao.GamePlay
{{
    public class {dataCodeFileName} : CFSMData
    {{
    }}
}}
";
            string dataCodePath = $"{codeDirPath}/{dataCodeFileName}.cs";
            if (!File.Exists(dataCodePath))
            {
                File.WriteAllText(dataCodePath, dataContent);
                UnityEngine.Debug.Log($"生成了代码：{dataCodeFileName}.cs");
            }
            AssetDatabase.Refresh();
        }

        //private string GetCfgNameByFileName(string classFileName)
        //{
        //    string name;
        //    //condition func
        //    if (classFileName.StartsWith(EdtCFSMConst.CondFuncCSFilePrefix))
        //    {
        //        name = classFileName.Replace(EdtCFSMConst.CondFuncCSFilePrefix + "_", string.Empty);
        //        if (name.StartsWith("_")) //公共条件代码文件名中间没有_作为连接符，如果有则为私有条件
        //        {
        //            int secIndex = name.IndexOf('_', 1);
        //            name = name.Substring(secIndex); //不用去掉连接符_，将就当做私有名称前缀
        //        }
        //    }
        //    //state
        //    else
        //    {
        //        //state一定是私有的
        //        name = classFileName.Replace(EdtCFSMConst.StateCSFilePrefix + "_", string.Empty);
        //        int secIndex = name.IndexOf('_');
        //        name = name.Substring(secIndex + 1); // +1去掉前面的_，这个下划线是作为链接符的
        //    }
        //    return name;
        //}

        /// <summary>
        /// 根据文件名获取配置里的名称
        /// 例如文件名是CFSMCond_Monster_AlertValue，配置名称应该是AlertValue
        /// </summary>
        /// <param name="classFileName"></param>
        /// <param name="prefix"></param>
        /// <returns></returns>
        private string GetCfgNameByFileName(string classFileName)
        {
            int lastIndex = classFileName.LastIndexOf('_');
            string name = classFileName.Substring(lastIndex + 1); // +1去掉前面的_，这个下划线是作为链接符的
            return name;
        }

        private string GetDataCodeFileName()
        {
            string dataModuleName = GetModulePath().Replace('/', '_');
            return $"CFSM{dataModuleName}Data";
        }
    }
}
